package electionCtrl;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.File;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;

import javax.swing.JOptionPane;
import javax.swing.JFileChooser;
import javax.xml.stream.XMLStreamException;

import xml.MockVotingXMLReader;
import xml.MockVotingXMLWriter;

import electionView.AdminGui;
import electionView.OverviewGui;
import electionView.PasswordGui;
import electionView.VoteFinishedGui;
import electionView.VoteGui;
import electionView.VoteReviewGui;

public class ElectionController {

	public enum State {SET_UP_ELECTION,OVERVIEW,VOTE,VOTE_CHECK,VOTE_FINISHED,ADMIN,ELECTION_END,PASSWORD_CHECK};
	private State state;
	private List<AdminGui> adminGuiList;
	private OverviewGui overviewGui;
	private VoteGui voteGui;
	private VoteFinishedGui voteFinishedGui;
	private PasswordGui passwordGui;
	private Election election;
	private Semaphore semaphore;
	
	public ElectionController(Election election)
	{
		this.election = election;
		this.adminGuiList = new LinkedList<AdminGui> ();
		this.semaphore = new Semaphore(0);
		for(Admin a : election.getCurrAdmins())
		{
			this.adminGuiList.add(new AdminGui(this.election, a, this.semaphore));
		}
		this.overviewGui = new OverviewGui(this.election, this.semaphore);
		this.overviewGui.deActivate();
		this.voteGui = new VoteGui(this.election, this.semaphore);
		this.passwordGui = new PasswordGui(this.election, this.semaphore);
		this.voteFinishedGui = new VoteFinishedGui(this.election);
		if(this.adminGuiList.isEmpty())
		{
			this.state = State.SET_UP_ELECTION;
		}
		else
		{
			this.state = State.OVERVIEW;
		}
	}
	
	public ElectionController(String loc) throws XMLStreamException, FileNotFoundException { //pass in a String in order to get something from the command line flag
		try {
			election = new Election(new MockVotingXMLReader(loc));
		} catch (XMLStreamException e) {
			JOptionPane.showMessageDialog(null, ".blt file is improperly formatted! The application will now terminate.");
			System.exit(0);
		} catch (FileNotFoundException e) {
			JOptionPane.showMessageDialog(null, "File not found! The application will now terminate.");
			System.exit(0);
		}
		
		adminGuiList = new LinkedList<AdminGui>();
		semaphore = new Semaphore(0);
		for(Admin a : election.getCurrAdmins())
		{
			adminGuiList.add(new AdminGui(election, a, semaphore));
		}
		overviewGui = new OverviewGui(election, semaphore);
		overviewGui.deActivate();
		voteGui = new VoteGui(election, semaphore);
		passwordGui = new PasswordGui(election, semaphore);
		voteFinishedGui = new VoteFinishedGui(election);
		if(adminGuiList.isEmpty()) {
			state = State.SET_UP_ELECTION;
		} else {
			state = State.OVERVIEW;
		}
	}
	
	public ElectionController() throws XMLStreamException, FileNotFoundException {//pass in absolutely nothing to be prompted for a file chooser to load an election
		this(findBallotFile());
	}
	
	private static final String findBallotFile()
	{
		JFileChooser chooser = new JFileChooser();
		JOptionPane.showMessageDialog(null, "Please select a correct ballot file (.blt).");
		chooser.showOpenDialog(null);
		String bltLoc = chooser.getSelectedFile().toString();
		String ext = bltLoc.substring((bltLoc.lastIndexOf('.')+1), bltLoc.length());
			while (!(ext.equals("blt"))) {
				JOptionPane.showMessageDialog(null, "You didn't select a ballot. Ballots are .blt files.");
				JOptionPane.showMessageDialog(null, "Please select a correct ballot file (.blt).");
				chooser.showOpenDialog(null);
				bltLoc = chooser.getSelectedFile().toString();
				ext = bltLoc.substring((bltLoc.lastIndexOf('.')+1), bltLoc.length());
			}
		return bltLoc;
	}
	
//	public ElectionController(Election election, Admin a)
//	{
//		this(election);
//		this.state = State.OVERVIEW;
//		if(!this.election.isLegalAdmin(a))
//		{
//			throw new IllegalArgumentException("The admin is not part of the election");
//		}
//		this.adminGuiList.add(new AdminGui(this.election, a));
//	}
	
	public void run() throws InterruptedException
	{
		AdminGui ag = null;
		State passwordCheckBeforeState = null;
		State passwordCheckAfterState = null;
		while(this.state != State.ELECTION_END)
		{
			switch(this.state)
			{
				case SET_UP_ELECTION:
					while(!this.setUpNewAdmin());
					this.state = State.OVERVIEW;
					break;
				case OVERVIEW:
					this.overviewGui.showWindow();
					this.overviewGui.enable();
					OverviewGui.Button ogbutton = this.overview();
					if(ogbutton == null)
					{
						continue;
					}
					this.overviewGui.disable();
					switch(ogbutton)
					{
						case ADMIN:
							passwordCheckBeforeState = State.OVERVIEW;
							passwordCheckAfterState = State.ADMIN;
							this.state = State.PASSWORD_CHECK;
							break;
						case BEGIN_VOTE:
							this.overviewGui.deActivate();
							this.overviewGui.hideWindow();
							this.state = State.VOTE;
							break;
						case REACTIVATE_SYSTEM:
							passwordCheckBeforeState = State.OVERVIEW;
							passwordCheckAfterState = State.OVERVIEW;
							this.state = State.PASSWORD_CHECK;
							break;
						case END_ELECTION:
							passwordCheckBeforeState = State.OVERVIEW;
							passwordCheckAfterState = State.ELECTION_END;
							this.state = State.PASSWORD_CHECK;
							break;
						default:
							break;
					}
					break;
				case VOTE:
					this.voteGui.showWindow();
					this.voteGui.enable();
					VoteGui.Button vgbutton = this.vote();
					if(vgbutton == null)
					{
						continue;
					}
					this.voteGui.disable();
					switch(vgbutton)
					{
						case REVIEW_VOTE:
							this.voteGui.hideWindow();
							this.state = State.VOTE_CHECK;
							break;
						default:
							break;
					}
					break;
				case VOTE_CHECK:
					VoteReviewGui.Button vrgbutton = this.reviewVote();
					switch(vrgbutton)
					{
						case CAST_VOTE:
							this.state = State.VOTE_FINISHED;
							break;
						case BACK:
							this.state = State.VOTE;
							break;
						default:
							break;
					}
					break;
				case VOTE_FINISHED:
					for(Vote v : this.voteGui.getVotes())
					{
						this.election.addVote(v);
					}
					this.voteGui.clearAllSelections();
					this.voteGui.clearAllVotes();
					this.voteFinishedGui.showWindow();
					this.voteFinishedGui.playSound();
					try
					{
						Thread.sleep(5000);
					}
					catch(InterruptedException e)
					{
						e.printStackTrace();
					}
					this.voteFinishedGui.hideWindow();
					this.state = State.OVERVIEW;
					break;
				case ADMIN:
					ag.showWindow();
					ag.enable();
					AdminGui.Button agbutton = this.admin(ag);
					if(agbutton == null)
					{
						continue;
					}
					ag.disable();
					switch(agbutton)
					{
						case RETURN_TO_MAIN_MENU:
							ag.hideWindow();
							ag = null;
							this.state = State.OVERVIEW;
							break;
						default:
							break;
					}
					break;
				case ELECTION_END:
					break;
				case PASSWORD_CHECK:
					this.passwordGui.showWindow();
					this.passwordGui.enable();
					PasswordGui.Button pgbutton = this.passwordCheck();
					this.passwordGui.disable();
					this.passwordGui.hideWindow();
					switch(pgbutton)
					{
						case OK:
							AdminGui agCheck = this.findAdmin();
							if(agCheck != null)
							{
								switch(passwordCheckAfterState)
								{
									case ADMIN:
										ag = agCheck;
										break;
									case OVERVIEW:
										this.overviewGui.reActivate();
										break;
									default:
										break;
								}
								this.state = passwordCheckAfterState;
								break;
							}
						case CANCEL:
						default:
							JOptionPane.showMessageDialog(null, "Access Denied: Authentication Failed.");
							this.state = passwordCheckBeforeState;
							break;
					}
					passwordCheckBeforeState = null;
					passwordCheckAfterState = null;
					break;
				default:
					break;
			}
		}
		//Election has ended
		this.election.setElection(false);
		try	{
			String filename = election.getFileName() + ".vot";
			
			while(new File(filename).exists()) {
				filename = election.getFileName() + ".vot";
			}
			
			election.writeVotesToXML(new MockVotingXMLWriter(filename));
			
		} catch (FileNotFoundException e) {
			JOptionPane.showMessageDialog(null, "File not found! The application will now terminate.");
			System.exit(0);
		} catch (XMLStreamException e) {
			JOptionPane.showMessageDialog(null, ".vot file improperly formatted! The application will now terminate.");
			System.exit(0);
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null, "Unable to read or write! The application will now terminate.");
			System.exit(0);
		}
	}
	
	private final boolean setUpNewAdmin() throws InterruptedException
	{
		JOptionPane.showMessageDialog(null, "Please input a username and password for the Administrator");
		PasswordGui.Button pgbutton = null;
		String [] userPass = new String [2];
		Admin a = null;
		while(a == null)
		{
			while(userPass[0] == null || userPass[0] == "" || userPass[1] == null || userPass[1] == "")
			{
				this.passwordGui.showWindow();
				this.passwordGui.enable();
				pgbutton = this.passwordGui.waitForAction();
				this.passwordGui.disable();
				this.passwordGui.hideWindow();
				if(pgbutton == PasswordGui.Button.CANCEL)
				{
					return false;
				}
				userPass = this.passwordGui.getUsernameAndPassword();
			}
			a = new Admin(userPass[0], userPass[1]);
			for(AdminGui ag : this.adminGuiList)
			{
				if(ag.getAdmin().checkUsername(a))
				{
					return false;
				}
			}
		}
		this.adminGuiList.add(new AdminGui(this.election, a, this.semaphore));
		this.election.addAdmin(a);
		return true;
	}
	
	private OverviewGui.Button overview() throws InterruptedException
	{
		OverviewGui.Button ogbutton = this.overviewGui.waitForAction();
		switch(ogbutton)
		{
			case ADMIN:
			case BEGIN_VOTE:
			case END_ELECTION:
			case REACTIVATE_SYSTEM:
				return ogbutton;
			default:
				break;
		}
		return null;
	}
	
	private VoteGui.Button vote() throws InterruptedException
	{
		VoteGui.Button vgbutton = this.voteGui.waitForAction();
		switch(vgbutton)
		{
			case REVIEW_VOTE:
				return vgbutton;
			default:
				break;
		}
		return null;
	}
	
	private VoteReviewGui.Button reviewVote() throws InterruptedException
	{
		VoteReviewGui vrg = new VoteReviewGui(this.election, this.voteGui.getVotes(), this.semaphore);
		vrg.showWindow();
		vrg.enable();
		VoteReviewGui.Button vrgbutton = vrg.waitForAction();
		vrg.disable();
		vrg.hideWindow();
		switch(vrgbutton)
		{
			case CAST_VOTE:
			case BACK:
				return vrgbutton;
			default:
				break;
		}
		return null;
	}
	
	private AdminGui.Button admin(AdminGui ag) throws InterruptedException
	{
		AdminGui.Button agbutton = ag.waitForAction();
		ag.disable();
		this.passwordGui.showWindow();
		this.passwordGui.enable();
		PasswordGui.Button pgbutton = this.passwordCheck();
		this.passwordGui.disable();
		this.passwordGui.hideWindow();
		AdminGui trial = this.findAdmin();
		if(trial == null || !trial.equals(ag) || pgbutton == PasswordGui.Button.CANCEL)
		{
			JOptionPane.showMessageDialog(null, "Access Denied: Authentication Failed");
			return null;
		}
		switch(agbutton)
		{
			case SET_USERNAME:
				String usernameReplacement = JOptionPane.showInputDialog("Please Enter a Username.");
				if(usernameReplacement != null && !usernameReplacement.equals(""))
				{
					if(this.findAdmin(new AdminGui(this.election, new Admin(usernameReplacement, null), this.semaphore)) == null)
					{
						ag.setUsername(usernameReplacement);
						JOptionPane.showMessageDialog(null, "Success!!!");
						break;
					}
					JOptionPane.showMessageDialog(null, "Error: Username already exists.");
					break;
				}
				JOptionPane.showMessageDialog(null,"Error: The username cannot be blank.");
				break;
			case SET_PASSWORD:
				String passwordReplacement = JOptionPane.showInputDialog("Please Enter a Password.");
				if(passwordReplacement != null && !passwordReplacement.equals(""))
				{
					ag.setPassword(passwordReplacement);
					JOptionPane.showMessageDialog(null, "Success!!!");
					break;
				}
				JOptionPane.showMessageDialog(null,"Error: The password cannot be blank.");
				break;
			case MAKE_NEW_ADMIN:
				if(!this.setUpNewAdmin())
				{
					JOptionPane.showMessageDialog(null,"Error: One or more of the fields are empty.  Please enter something into the field.");	
				}
				break;
			case RETURN_TO_MAIN_MENU:
				return agbutton;
			default:
				break;
		}
		return null;
	}
	
	private PasswordGui.Button passwordCheck() throws InterruptedException
	{
		PasswordGui.Button pgbutton = this.passwordGui.waitForAction();
		switch(pgbutton)
		{
			case OK:
			case CANCEL:
				return pgbutton;
			default:
				break;
		}
		return null;
	}
	
	private AdminGui findAdmin()
	{
		String [] userPass = this.passwordGui.getUsernameAndPassword();
		AdminGui ag = this.findAdmin(userPass[0], userPass[1]);
		return ag;
	}
	
	private AdminGui findAdmin(String username, String password)
	{
		AdminGui ag = new AdminGui(this.election, new Admin(username, password), this.semaphore);
		return this.findAdmin(ag);
	}
	
	private AdminGui findAdmin(AdminGui ag)
	{
		if(this.adminGuiList.contains(ag))
		{
			for(AdminGui agReal : this.adminGuiList)
			{
				if(ag.equals(agReal))
				{
					return agReal;
				}
			}
		}
		return null;
	}
}
